package top.v5it.japi.plus.data.jpa.repository.support;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.util.ProxyUtils;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import top.v5it.japi.plus.data.jpa.repository.JapiJpaRepository;
import top.v5it.japi.plus.data.jpa.repository.query.JapiQueryUtil;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import java.util.*;
import java.util.stream.Stream;

/**
 * 实现{@link JapiJpaRepository}
 *
 * @author zhanpu
 * @date 2021/3/19
 */
@Repository
@Transactional(readOnly = true)
public class JapiSimpleJpaRepository<T, ID> extends SimpleJpaRepository<T, ID> implements JapiJpaRepository<T, ID> {

    private final JpaEntityInformation<T, ?> entityInformation;
    private final EntityManager em;

    public JapiSimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityInformation = entityInformation;
        this.em = entityManager;
    }

    public JapiSimpleJpaRepository(Class<T> domainClass, EntityManager em) {
        this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
    }

    @Transactional
    @Override
    @SuppressWarnings("unchecked")
    public <S extends T> S saveOrDynamicUpdate(S entity) {

        ID id = (ID) this.entityInformation.getId(entity);
        if (Objects.isNull(id)) {
            this.em.persist(entity);
            return entity;
        } else {

            // 查询对象
            T existing = super.findById(id).orElseThrow(() -> new IllegalArgumentException(String.format("待更新的%s对象不存在!", entity)));

            // 获取类型
            Class<?> type = ProxyUtils.getUserClass(entity);
            // copy新值给查询对象，这样疔以再利用@DynamicUpdate实现动态更新
            BeanUtil.copyProperties(entity, existing, CopyOptions.create(type, true));
            return (S) this.em.merge(existing);
        }
    }

    @Transactional
    @Override
    public <S extends T> S save(S entity, boolean isOverride) {
        if (isOverride) {
            super.save(entity);
        } else {
            this.em.persist(entity);
        }

        return entity;
    }

    @Transactional
    @Override
    public <S extends T> List<S> saveBatch(Iterable<S> entities, int batchSize) {
        Assert.notNull(entities, "Entities must not be null!");
        List<S> result = new ArrayList<>();
        Iterator<S> iterator = entities.iterator();

        int size = IterUtil.size(entities);
        int i = 1;

        while (iterator.hasNext()) {
            S entity = iterator.next();
            this.em.persist(entity);

            if (i % batchSize == 0 || i == size) {
                this.em.flush();
                this.em.clear();
            }

            i++;

            result.add(entity);
        }

        return result;
    }

    @Transactional
    @Override
    public <S extends T> List<S> updateBatch(Iterable<S> entities, int batchSize) {
        Assert.notNull(entities, "Entities must not be null!");
        List<S> result = new ArrayList<>();
        Iterator<S> iterator = entities.iterator();

        int size = IterUtil.size(entities);
        int i = 1;

        while (iterator.hasNext()) {
            S entity = iterator.next();
            this.em.merge(entity);

            if (i % batchSize == 0 || i == size) {
                this.em.flush();
                this.em.clear();
            }

            i++;

            result.add(entity);
        }

        return result;
    }

    @Transactional
    @Override
    public <T> void deleteBatch(Iterable<T> entities, int batchSize) {
        Assert.notNull(entities, "Entities must not be null!");
        Iterator<T> iterator = entities.iterator();

        int size = IterUtil.size(entities);
        int i = 1;

        while (iterator.hasNext()) {
            T entity = iterator.next();
            this.em.remove(entity);

            if (i % batchSize == 0 || i == size) {
                this.em.flush();
                this.em.clear();
            }

            i++;
        }
    }

    @Transactional
    @Override
    @SuppressWarnings("unchecked")
    public <S extends T> S dynamicUpdate(S entity, String... whereProperty) {

        // 获取id
        ID id = (ID) this.entityInformation.getId(entity);

        // 断言
        Assert.isFalse(Objects.isNull(id) && Stream.of(whereProperty).allMatch(StrUtil::isBlank), "实体主键与条件参数为空");

        // id不为空按id更新，否则按条件字段更新
        Iterable<String> iterable = Objects.nonNull(id) ? this.entityInformation.getIdAttributeNames() : Arrays.asList(whereProperty);

        // 将实体对象转换成功map
        Map<String, Object> entityMap = BeanUtil.beanToMap(entity, false, true);
        String entityName = this.entityInformation.getEntityName();
        // 根据实体对象字段信息过虑掉null值，拼接hql语句，以id对条件
        String updateString = JapiQueryUtil.getDynamicUpdateString(entityName, iterable, entityMap);
        JapiQueryUtil.applyAndBind(updateString, entityMap, this.em).executeUpdate();

        return entity;
    }

    @Override
    public <S extends T> List<S> dynamicUpdateBatch(Iterable<S> entities, int batchSize, String... whereProperty) {
        Assert.notNull(entities, "Entities must not be null!");
        List<S> result = new ArrayList<>();
        Iterator<S> iterator = entities.iterator();

        int size = IterUtil.size(entities);
        int i = 1;

        EntityTransaction transaction = this.em.getTransaction();
        // 开启事务
        transaction.begin();

        while (iterator.hasNext()) {

            S entity = iterator.next();
            this.dynamicUpdate(entity, whereProperty);

            if (i % batchSize == 0 || i == size) {
                // 提交事务
                transaction.commit();

                // 有数据开启事务
                if (iterator.hasNext()) {
                    transaction.begin();
                }
            }

            i++;

            result.add(entity);
        }

        return result;
    }
}
